package org.springframe.context;

import org.springframe.annotation.Aspect;
import org.springframe.annotation.Autowired;
import org.springframe.annotation.Component;
import org.springframe.annotation.ComponentScan;
import org.springframe.bean.BeanDefinition;
import org.springframe.constants.ScopeConstants;
import org.springframe.factory.BeanNameAware;
import org.springframe.factory.InitializingBean;
import org.springframe.postProcessor.AopProxyCreatorBeanPostProcessor;
import org.springframe.postProcessor.BeanPostProcessor;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class AbstractApplicationContext implements ApplicationContext {

    private Map<String,Object> singletonMap = new ConcurrentHashMap<>();

    private Map<String,Object> earlySingletonMap = new HashMap<>();

    private Map<String, Supplier<Object>> singleFactoryMap = new HashMap<>();

    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    private Set<Class> aspectClassSet = new HashSet<>();

    private Set<Class> aspectClassDeclaredSet = new HashSet<>();

    private Class<?> configClass;

    public AbstractApplicationContext(Class<?> configClass) {
        this.configClass = configClass;
    }

    /**
     * 获取bean,并将最终bean放入一级缓存
     * @param beanName
     * @param factory
     * @return
     * @throws Exception
     */
    private Object getSingleton(String beanName,Supplier<Object> factory) throws Exception{
        synchronized (singletonMap){
            Object instance = singletonMap.get(beanName);
            if(instance==null){
                instance = factory.get();
                singletonMap.put(beanName,instance);
            }
            return instance;
        }
    }

    /**
     * 从缓存中取 一级---》二级----》三级
     * @param beanName
     * @return
     */
    private Object getInstance(String beanName){
        Object instance;
        if((instance = singletonMap.get(beanName))==null){
            synchronized (singletonMap){
                if((instance = earlySingletonMap.get(beanName))==null){
                    Supplier<Object> sinFactory = singleFactoryMap.get(beanName);
                    if(sinFactory!=null){
                        instance = sinFactory.get();
                        earlySingletonMap.put(beanName,instance);
                        singleFactoryMap.remove(beanName);
                    }
                }
            }
        }
        return instance;
    }

    @Override
    public Object getBean(String beanName) throws Exception{
        BeanDefinition beanDefinition;Object instance;
        if((beanDefinition =  beanDefinitionMap.get(beanName))==null){
            throw new RuntimeException("no bean found for beanName is "+beanName);
        }
        instance = getInstance(beanName);
        if(instance==null){
           if(isSingleton(beanName)){
               instance = getSingleton(beanName, () -> {
                   try {
                      return createBean(beanName,beanDefinition);
                   } catch (Exception e) {
                       e.printStackTrace();
                   }
                   return null;
               });

           }else if(isPrototype(beanName)){
               instance = createBean(beanName,beanDefinition);
           }
        }
        return instance;
    }

    @Override
    public boolean isSingleton(String var1) {
        BeanDefinition beanDefinition;
        if((beanDefinition =  beanDefinitionMap.get(var1))==null){
            throw new RuntimeException("no bean found for beanName is "+var1);
        }
        return ScopeConstants.SCOPE_SINGLETION.equals(beanDefinition.getScope());
    }

    @Override
    public boolean isPrototype(String var1) {
        BeanDefinition beanDefinition;
        if((beanDefinition =  beanDefinitionMap.get(var1))==null){
            throw new RuntimeException("no bean found for beanName is "+var1);
        }
        return ScopeConstants.SCOPE_PROTOTYPE.equals(beanDefinition.getScope());
    }

    //如果需要事务代理
    public Object getEarlyBeanReference(String beanName,Object bean){
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            if(beanPostProcessor instanceof AopProxyCreatorBeanPostProcessor){
                try {
                    bean =  beanPostProcessor.postProcessAfterInitialization(bean,beanName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }
        return bean;
    }

    /**
     * 模拟创建Bean的生命周期
     * 1.实例化前
     * 2.实例化
     * 3.实例化后
     * 4.属性填充
     * 5.Aware回调
     * 6.初始化前
     * 7.初始化
     * 8.初始化后
     * 9.AOP代理
     * @return
     */
    public Object createBean(String beanName,BeanDefinition beanDefinition) throws Exception{
        Class clazz = beanDefinition.getBeanClass();
        Object instance = singletonMap.get(beanName);
        if(instance!=null){
            return instance;
        }
        //实例化
        if(clazz.isAssignableFrom(AopProxyCreatorBeanPostProcessor.class)){
            instance = clazz.getDeclaredConstructor(Set.class,Set.class).newInstance(aspectClassSet,aspectClassDeclaredSet);
        }else{
            instance = clazz.getDeclaredConstructor().newInstance();
        }
        //存入三级缓存
        synchronized (singletonMap) {
            if (!singletonMap.containsKey(beanName)) {
                Object finalInstance = instance;
                this.singleFactoryMap.put(beanName, () -> getEarlyBeanReference(beanName, finalInstance));
                this.earlySingletonMap.remove(beanName);
            }
        }
        //属性填充
        instance = popularBean(instance,beanDefinition);
        //Aware回调
        if(instance instanceof BeanNameAware){
            ((BeanNameAware) instance).setBeanName(beanName);
        }
        //  初始化前
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            instance = beanPostProcessor.postProcessBeforeInitialization(instance,beanName);
        }
        //初始化
        if(instance instanceof InitializingBean){
            ((InitializingBean) instance).afterPropertiesSet();
        }
        //  初始化后
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            // 可以生成对应的代理对象， //AOP
            instance = beanPostProcessor.postProcessAfterInitialization(instance,beanName);
        }
        Object earlySingletonReference = singletonMap.get(beanName);
        if(earlySingletonReference ==null){
            synchronized (singletonMap){
                earlySingletonReference = earlySingletonMap.get(beanName);
            }
        }
        if(earlySingletonReference!=null){
            instance = earlySingletonReference;
        }
        return instance;
    }

    public Object popularBean(Object bean,BeanDefinition beanDefinition) throws Exception{
        for (Field declaredField : beanDefinition.getBeanClass().getDeclaredFields()) {
            if(declaredField.isAnnotationPresent(Autowired.class)){
                Autowired autowired = declaredField.getDeclaredAnnotation(Autowired.class);
                Object value = getBean(declaredField.getName());
                if(autowired.required()){
                    if(value==null){
                        throw new RuntimeException(declaredField.getName()+" is not found!");
                    }
                }
                declaredField.setAccessible(true);
                declaredField.set(bean,value);
            }
        }
        return bean;
    }

    /**
     * 加载class生成对应的beanDefinition集合
     */
    public void loadBeanDefinition() throws Exception{
        if( configClass.isAnnotationPresent(ComponentScan.class)){
            ComponentScan componentScan = configClass.getDeclaredAnnotation(ComponentScan.class);
            String basePackage = componentScan.value();
            String basePath = basePackage.replace(".","/");
            Enumeration<URL> urls= getClass().getClassLoader().getResources(basePath);
            while (urls!=null && urls.hasMoreElements()) {
                URL url = urls.nextElement();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    String file = URLDecoder.decode(url.getFile(), "UTF-8");
                    File dir = new File(file);
                    if(dir.isDirectory()){
                        parseClassFile(dir,basePackage);
                    }else {
                        throw new IllegalArgumentException("file must be directory");
                    }
                }
            }
        }
        // 根据beanDefinition 创建bean
        beanDefinitionMap.forEach((key,value) ->{
            if(BeanPostProcessor.class.isAssignableFrom(value.getBeanClass())){
                try {
                    BeanPostProcessor beanPostProcessor = (BeanPostProcessor) getBean(key);
                    beanPostProcessorList.add(beanPostProcessor);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        beanDefinitionMap.forEach((key,value) ->{
            Object instance = null;
            try {
                instance = getBean(key);
            } catch (Exception e) {
                e.printStackTrace();
            }
            singletonMap.put(key,instance);
            synchronized (singletonMap){
                earlySingletonMap.remove(key);
                singleFactoryMap.remove(key);

            }
        });
    }

    protected void parseClassFile(File dir, String packageName) throws ClassNotFoundException {
        if(dir.isDirectory()){
            File[] files = dir.listFiles();
            for (File file : files) {
                parseClassFile(file, packageName);
            }
        } else if(dir.getName().endsWith(".class")) {
            String className = dir.getPath();
            className = className.substring(className.indexOf("classes")+8,className.indexOf(".class")).replace("\\", ".");
            Class clazz =  getClass().getClassLoader().loadClass(className);
            //AOP PostProcessor
            BeanDefinition aopbd = new BeanDefinition();
            aopbd.setBeanClass(AopProxyCreatorBeanPostProcessor.class);
            aopbd.setScope(ScopeConstants.SCOPE_SINGLETION);
            beanDefinitionMap.put(AopProxyCreatorBeanPostProcessor.class.getName(),aopbd);
            if(clazz.isAnnotationPresent(Component.class)){
                // 生成 BeanDefinition
                Component component = (Component) clazz.getDeclaredAnnotation(Component.class);
                BeanDefinition bd = new BeanDefinition();
                bd.setBeanClass(clazz);
                bd.setScope(component.scope());
                beanDefinitionMap.put(component.value(),bd);
            }
            if(clazz.isAnnotationPresent(Aspect.class)){
                Aspect aspect = (Aspect) clazz.getDeclaredAnnotation(Aspect.class);
                aspectClassSet.addAll(Arrays.stream(aspect.value()).collect(Collectors.toSet()));
                aspectClassDeclaredSet.add(clazz);
            }
        }
    }
}
